package com.bianmaba.spring.mvc.method.support;

import com.bianmaba.spring.mvc.configure.ExtendedWebMvcAutoConfiguration;
import com.bianmaba.spring.mvc.method.support.annotations.JSON;
import com.bianmaba.spring.mvc.method.support.annotations.StrutsJSON;
import org.apache.struts2.json.JSONException;
import org.apache.struts2.json.JSONWriter;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;

/**
 * Created by cwx183898 on 2017/8/18.
 * <pre>
 * 此返回数据处理器在 {@link ExtendedWebMvcAutoConfiguration}中使用@Import创建实例
 * </pre>
 */
public class StrutsJsonHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler, BeanPostProcessor {

    List<ResponseBodyAdvice<Object>> advices = new ArrayList<>();

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        // 如果有我们自定义的 JSON 注解 就用我们这个Handler 来处理
        return returnType.hasMethodAnnotation(JSON.class) || returnType.hasParameterAnnotation(JSON.class) || returnType.hasMethodAnnotation(StrutsJSON.class) || returnType.hasParameterAnnotation(StrutsJSON.class);
    }

    protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
        HttpServletResponse response = (HttpServletResponse) webRequest.getNativeResponse(HttpServletResponse.class);
        return new ServletServerHttpResponse(response);
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest) throws IOException {

        try {
            mavContainer.setRequestHandled(true);
            ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest);

            MediaType mediaType = outputMessage.getHeaders().getContentType();
            Charset charset = this.getJsonEncoding(mediaType);
            String contentType = "application/json";
            if (charset != null) {
                contentType += ";charset=" + charset.name();
            }
            mediaType = MediaType.valueOf(contentType);

            for (int i = 0; i < this.advices.size(); i++) {
                ResponseBodyAdvice<Object> ad = this.advices.get(i);
                if (ad.supports(returnType, null)) {
                    returnValue = ad.beforeBodyWrite(returnValue, returnType, mediaType, null,
                            new ServletServerHttpRequest(webRequest.getNativeRequest(HttpServletRequest.class)),
                            new ServletServerHttpResponse(webRequest.getNativeResponse(HttpServletResponse.class)));
                }
            }

            List<Pattern> includeProperties = Collections.emptyList();
            List<Pattern> excludeProperties = null;
            boolean ignoreHierarchy = false;
            boolean enumAsBean = false;
            boolean excludeNullProperties = false;
            //获取方法注解或参数注解
            JSON annotation = returnType.hasMethodAnnotation(JSON.class) ? returnType.getMethodAnnotation(JSON.class) : returnType.getParameterAnnotation(JSON.class);
            includeProperties = createPatterns(annotation.includeProperties());
            excludeProperties = createPatterns(annotation.excludeProperties());
            ignoreHierarchy = annotation.ignoreHierarchy();
            enumAsBean = annotation.enumAsBean();
            excludeNullProperties = annotation.excludeNullProperties();

            JSONWriter writer = new JSONWriter();
            writer.setIgnoreHierarchy(ignoreHierarchy);
            writer.setEnumAsBean(enumAsBean);
            String json = writer.write(returnValue, excludeProperties, includeProperties, excludeNullProperties);
            outputMessage.getServletResponse().setContentType(contentType);
            outputMessage.getBody().write(json.getBytes(charset.name()));
            outputMessage.getBody().flush();
        } catch (JSONException e) {
            throw new HttpMessageNotWritableException("JSON数据写入失败：序列化JSON数据出现异常！", e);
        } catch (Exception e) {
            throw new HttpMessageNotWritableException("JSON数据写入失败： 处理过程中发生异常！", e);
        }
    }

    private List<Pattern> createPatterns(String[] arr) {
        if (arr == null || arr.length <= 0) {
            return null;
        }
        List<Pattern> list = new ArrayList<Pattern>(0);
        for (String item : arr) {
            list.add(Pattern.compile(item));
        }
        return list;
    }

    protected Charset getJsonEncoding(MediaType contentType) {
        if (contentType != null && contentType.getCharset() != null) {
            Charset charset = contentType.getCharset();
            return charset;
        }
        Charset charset = Charset.defaultCharset();
        return charset;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof ResponseBodyAdvice) {
            this.advices.add((ResponseBodyAdvice<Object>) bean);
        } else if (bean.getClass().isAssignableFrom(RequestMappingHandlerAdapter.class)) {
            RequestMappingHandlerAdapter requestMappingHandlerAdapter = ((RequestMappingHandlerAdapter) bean);
            List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(requestMappingHandlerAdapter.getReturnValueHandlers());
            StrutsJsonHandlerMethodReturnValueHandler targetHandler = null;

            for (int i = handlers.size() - 1; i >= 0; i--) {
                HandlerMethodReturnValueHandler handler = handlers.get(i);
                if (handler.getClass().isAssignableFrom(StrutsJsonHandlerMethodReturnValueHandler.class)) {
                    targetHandler = (StrutsJsonHandlerMethodReturnValueHandler) handler;
                    break;
                }
            }
            if (targetHandler != null) {
                handlers.remove(targetHandler);
                handlers.add(0, targetHandler);
                requestMappingHandlerAdapter.setReturnValueHandlers(handlers); // change the jsonhandler sort
            }
        }
        return bean;
    }
}
